package org.es.io;

/**
 * Copyright (C) 2012 Eduard Sedov
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;

/**
 *
 * @author Eduard Sedov
 */
public class PagedFile {

    private File file;
    private FileChannel fileChannel;
    private long pageSize;
    private long firstPageOffset;

    public PagedFile(File file, long pageSize) throws FileNotFoundException {
        this(file, pageSize, 0);
    }

    public PagedFile(File file, long pageSize, long firstPageOffset) throws FileNotFoundException {
        this.file = file;
        fileChannel = new RandomAccessFile(file, "rw").getChannel();
        this.pageSize = pageSize;
        this.firstPageOffset = firstPageOffset;
    }

    public File file() {
        return file;
    }

    public FileChannel fileChannel() {
        return fileChannel;
    }

    public ByteBuffer get(long page) {
        try {
            ByteBuffer result = ByteBuffer.allocate((int) pageSize);
            long pageOffset = firstPageOffset + page * pageSize;
            fileChannel.position(pageOffset);
            int count = 0;
            int bytesRead;
            while ((bytesRead = fileChannel.read(result)) != -1
                    && (count += bytesRead) < pageSize) {
            }
            result.position(0);
            return result;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void write(ByteBuffer buffer, long page) {
        try {
            buffer.position(0);
            long pageOffset = firstPageOffset + page * pageSize;
            fileChannel.position(pageOffset);
            int bytesWriten = 0;
            while ((bytesWriten += fileChannel.write(buffer)) < pageSize
                    && buffer.hasRemaining()) {
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public MappedByteBuffer getMemoryMapped(long page) {
        try {
            long pageOffset = firstPageOffset + page * pageSize;
            try {
                return fileChannel.map(MapMode.READ_WRITE, pageOffset, pageSize);
            } catch (OutOfMemoryError e) {
                System.gc();
                return fileChannel.map(MapMode.READ_WRITE, pageOffset, pageSize);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void close() throws IOException {
        fileChannel.close();
    }

    public long size() {
        try {
            return fileChannel.size() / pageSize;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void truncate(long page) {
        try {
            fileChannel.truncate(firstPageOffset + page * pageSize);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
